/*********************************************************************************
 *      Copyright:  (C) 2022 xiezongyi<1764417007@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(21/07/22)
 *         Author:  xiezongyi <1764417007@qq.com>
 *      ChangeLog:  1, Release initial version on "21/07/22 14:34:20"
 *                 
 ********************************************************************************/
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <libgen.h>
#include <sqlite3.h>
#include <arpa/inet.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>

#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>

#include "CRC.h"
#include "server.h"
#include "tlv_server.h"
#include "ssl_server.h"
#include "database_server.h"

int main (int argc, char **argv)
{
	int						ch;
	int						port = 0;
	int						daemon_run = 0;
	sqlite3					*db;
	SSL_CTX                 *ctx;
	struct event_base		*base = NULL;
	struct evconnlistener	*listener = NULL;
	struct sockaddr_in		servaddr;

    struct option           opts[] =
    {
        {"port", required_argument, NULL, 'p'},
		{"daemon", no_argument, NULL, 'd'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };

    char *progname = basename(argv[0]);

    while((ch = getopt_long(argc, argv, "p:dh", opts, NULL)) != -1)
    {
        switch(ch)
        {
            case 'p':
                port = atoi(optarg);
                break;
			case 'd':
				daemon_run = 1;
				break;
            case 'h':
                print_usage(progname);
                return 0;
        }
    }

    if(!port)
    {
        print_usage(progname);
        return 0;
	}

	if(daemon_run)
	{
		daemon(1, 0);
	}

	if( database_init(&db) < 0 )
	{
		printf("Open database failure: %s\n", strerror(errno));
		return -1;
	}

	ctx = evssl_init();
    if(!ctx)
    {
        printf("evssl_init() failure: %s\n", strerror(errno));
        return -1;
    }

	//创建event_base
	base = event_base_new();
	if(!base)
	{
		printf("Create a base failure: %s\n", strerror(errno));
		return -1;
	}

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	//创建一个连接监听器：evconnlistener
	listener = evconnlistener_new_bind(base, accept_conn_cb, (void *)db, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr *)&servaddr, sizeof(servaddr));
	if(!listener)
	{
		printf("Create a evconnlistener failure: %s\n", strerror(errno));
		return -2;
	}
	
	//设置一个错误回调函数，每次监听器出错该函数都会返回
	evconnlistener_set_error_cb(listener, accpet_error_cb);

	//事件循环
	event_base_dispatch(base);	

	SSL_CTX_free(ctx);

	return 0;
} 

//设置evconnlistener收到连接时调用的回调函数
//一个新的客户端连接上服务器了，此时该回调函数被调用
//libevent已经帮我们accept了这个客户端，该客户端的文件描述符为：clifd
void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t clifd, struct sockaddr * cliaddr, int cliaddr_len, void *ctx) 
{
	struct sockaddr_in *addr = (struct sockaddr_in *)cliaddr;
	printf("Accept socket[%d] on port[%d] successfully!\n", clifd, addr->sin_port);

	SSL_CTX *server_ctx = (SSL_CTX *)ctx;
    SSL     *client_ctx = SSL_new(server_ctx);//创建一个ssl的实例，这个实例后续握手、读写、关闭等需要

	//获取evconnlistener绑定的event_base	
	struct event_base *base = evconnlistener_get_base(listener);

	//为这个客户端分配一个bufferevent
	struct bufferevent *bev = bufferevent_openssl_socket_new(base, clifd, client_ctx, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE);

	bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL);
	bufferevent_enable(bev, EV_READ);
}

//设置evconnlistener出错时的回调函数
void accpet_error_cb(struct evconnlistener *listener, void *arg)
{
	//获取evconnlistener绑定的event_base
	struct event_base *base = evconnlistener_get_base(listener);

	//返回当前线程最后一次套接字操作的错误号
	int		err = EVUTIL_SOCKET_ERROR();

	printf("Got an error number[%d] is: %s\n", err, evutil_socket_error_to_string(err));
	event_base_loopexit(base, NULL);
}

//设置bufferevent的读回调
void read_cb(struct bufferevent *bev, void *arg) 
{
	int         tlv_size;
	char 		msg[128];
    char        tlv_buf[256];

	//sqlite3 *db = (void *)arg;
	
	bzero(msg, sizeof(msg));
	size_t len = bufferevent_read(bev, msg, sizeof(msg));
	parser_tlvpack(msg, len, tlv_buf, &tlv_size);
	dump_buf(tlv_buf, tlv_size);

#if 0
	if( save_data_to_database(db, msg) < 0 )
	{
		printf("Save data to database failure: %s\n", strerror(errno));
	}
#endif
}

//设置bufferevent的事件回调
void event_cb(struct bufferevent *bev, short event, void *arg)
{
    if(event & BEV_EVENT_TIMEOUT)
    {
        printf("Time out\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
        printf("\nStart waiting next connect......\n");
    }
    else if(event & BEV_EVENT_ERROR)
    {
        printf("some other error\n");
    }
	
	//这将自动close套接字和free读写缓冲区
	bufferevent_free(bev);
}

void print_usage(char *progname)
{
    printf("%s usage:\n", progname);
    printf("-p(--port):specify server port\n");
	printf("-d(--daemon): process running in the background\n");
    printf("-h(--help):print this help information\n");

    return;
}

